Pro ASP.NET Core MVC2(第7版)翻译

第2章:第一个 MVC 应用程序

作者:Adam Freeman 翻译:陈广 日期:2018-8-18


欣赏软件开发框架的最好方法是直接进入并使用它。在本章,您将使用 ASP.NET Core MVC 创建一个简单的数据输入应用程序。我一步一步地操作,这样您就可以看到 MVC 应用程序是如何构造的。为了保持简单,我暂时跳过一些技术细节。但是不要担心。如果您是 MVC 的新手,会发现很多让你感兴趣的东西。本章使用了一些未解释的东西,为此我提供了一个参考章节,您可以在其中找到所有的细节。

本书的更新: 微软对 .NET core 和 ASP.NET Core MVC 有一个积极的开发计划,这意味着在您阅读这本书时可能会有新的版本可供使用。期望读者每隔几个月购买一本新书似乎不公平,特别大多数变化相对较小。相反,我将免费更新本书的GitHub仓库(https://github.com/apress/pro-asp.net-core-mvc-2),以打破由小版本引起的更改。

对我来说,这种更新是一个实验,我还不知道这些更新可能会采取什么形式 —— 尤其是因为我不知道ASP.NET Core MVC的未来主要版本将包含什么 —— 但目标是通过补充它所包含的示例来延长这本书的寿命。

我不会保证更新会是什么样子,它们将采取什么形式,或者我会花多长时间把它们转化成这本书的新版本。当新的 ASP.NET Core MVC版本发布时,请保持开放的心态,查看这本书的仓库。如果您对如何改进更新有想法,那么请将电子邮件发送到 adam@adam-freeman.com,并让我知道。

安装 Visual Studio

这本书依赖于 Visual Studio 2017,它为 ASP.NET Core MVC项目提供了开发环境。我使用免费的Visual Studio 2017 社区版,它可以从www.visualstudio.com下载。在安装 Visual Studio 2017 时,必须选择 .NET Core 跨平台开发工作负载,如图2-1所示。

注意:Visual Studio 2017 早于 ASP.NET Corer MVC 2的发布。如果已为 ASP.NET Core MVC 的早期版本安装了Visual Studio,则必须应用最新的更新。您可以通过运行Visual Studio安装程序并为正在使用的Visual Studio版本选择 “Update” 来应用更新。

图2-1 选择 Visual Studio 工作负载

提示:Visual Studio 仅支持 Windows。您可以使用 Visual Studio Code 在其他平台上创建ASP.NET Core MVC应用程序。Visual Studio Code 并不提供 Visual Studio 的所有功能,但它是一个优秀的编辑器,它拥有开发 MVC 应用程序所需的一切。请参阅第13章。

安装 .NET Core 2.0 SDK

Visual Studio 安装包括 ASP.NET Core MVC 开发所需的所有功能,但不包括.NET Core 2,必须单独下载和安装。

进入 https://www.microsoft.com/net/core,下载并运行 Windows 的 .NET Core SDK 安装程序。安装程序完成后,新开一个命令提示符或 PowerShell 窗口,运行以下命令显示已安装的 .NET 版本:

dotnet --version

如果安装成功,此命令的结果将为2.0.0

创建新的 ASP.NET Core MVC 项目

从在 Visual Studio 中创建一个新的 ASP.NET Core MVC 项目开始。从【File】菜单中选择【New】➤【Project】来打开【New Project】对话框。如果您在左边面板导航到【Installed】➤【Visual C#】➤【Web】部分,将会看到【ASP.NET Core Web Application (.NET Core)】项目模板。选择这个项目类型,如图2-2所示。

提示:项目模板的选择可能会令人困惑,因为它们的名称非常相似。ASP.NET Web应用程序(.NET Framework)模板用于使用 ASP.NET 和 MVC 框架的遗留版本创建项目,后者早于ASP.NET Coer。另外两个模板用于创建 ASP.NET Coer 应用程序,它们使用的运行时不同,允许您选择.NET Framework或.NET Core。我会在第6章中解释它们之间的不同之处,但是我在本书中使用了.NET Core选项,所以您应该选择它,以确保从示例应用程序中获得相同的结果。

图2-2 ASP.NET Core Web 应用程序项目模板

在新项目的【Name】栏中输入 PartyInvites。单击【OK】按钮继续,您将看到另一个对话框,如图2-3所示,该对话框要求您设置项目的初始内容。请确保从下拉菜单中选择了 .NET Core 和 ASP.NET Core 2.0,如图所示。 图2-3 选择项目初始配置

有几个模板选项,每个模板选项创建一个具有不同启动内容的项目。本章选择 web应用程序【Web Application (Model-View-Controller)】选项,它设置带有预定义内容的 MVC 应用程序,以启动开发。

注意:这是我使用【Web Application (Model-View-Controller)】项目模板的唯一章。我不喜欢使用预定义项目模板,因为它们鼓励开发人员将一些重要的特性(如身份验证)作为黑箱来处理。我这本书中的目标是让您了解和管理 MVC 应用程序的每个方面,所以我在其余部分使用空白模板。这一章是关于快速入门的,其中【Web Application (Model-View-Controller)】模板非常适合。

单击【Change Authentication】按钮,确保选中了【No Authentication】选项,如图2-4所示。该项目不需要任何身份验证,但我会在第28、29和30章中解释如何保护 ASP.NET 应用程序。 图2-4 选择身份验证配置

单击【OK】以关闭【Change Authentication】对话框。请确保未选中【Enable Docker Support】选项,然后单击【OK】以创建【PartyInvites】项目。

一旦 Visual Studio 创建了该项目,您将在【Solution Explorer】窗口中看到许多文件和文件夹,如图2-5所示。这是使用【Web Application (Model-View-Controller)】模板创建的新 MVC 项目的默认项目结构,您将很快了解 Visual Studio 创建的每个文件和文件夹的用途。

提示:如果您看到的是一个【Pages】文件夹,而不是【Controllers】、【Models】和【Views】文件夹,那么您已经选择了【Web Application】模板,而不是【Web Application (Model-View-Controller)】模板(令人困惑的相似的)。我不知道为什么微软认为这样的类似名称是个好主意,但是您必须删除您创建的项目并重新开始。

图2-5 ASP.NET Core MVC项目的初始文件和目录结构

您可以通过从【Debug】菜单中选择【Start Debugging】(如果它提示您启用调试,只需单击【OK】按钮)来运行应用程序。当您这样做时,Visual Studio 编译应用程序,使用名为IIS Express的应用服务器运行该应用程序,并打开 Web 浏览器请求应用程序内容。在 Visual Studio 第一次运行该项目时,可能需要花费一些时间,当流程结束,您将看到如图2-6所示的结果。 图2-6 运行示例项目

当 Visual Studio 使用【Web Application (Model-View-Controller)】模板创建一个项目时,它添加了一些基本代码和内容,这就是您在运行应用程序时所看到的。在本章的其余部分,我将替换这个内容来创建一个简单的 MVC 应用程序。

完成调试后,请关闭浏览器窗口或返回 Visual Studio 并从【Debug】菜单中选择【Stop Debugging】,以确保停止调试。

正如您刚才看到的, Visual Studio 打开浏览器以显示项目。您可以通过单击【IIS Express】工具栏按钮右侧的箭头并从【Web Browser】菜单中的选项列表中选择已安装的任何浏览器,如图2-7所示。 图2-7 选择一个浏览器

从现在开始,这本书的所有屏幕截图将使用Google Chrome或Google Chrome Canary,但您可以使用任何现代浏览器显示书中的例子,包括微软 Edge。

添加一个控制器

在 MVC 模式中,传入的请求由控制器处理。在 ASP.NET Core MVC,控制器仅是一个 C# 类(通常继承自内置于 MVC 控制器的基类:Microsoft.AspNetCore.Mvc.Controller)。

控制器中的每个公共方法都被称为 action方法,这意味着您可以通过 URL 从 Web 调用它来执行操作。MVC 的约定是将控制器放在【Controllers】文件夹中,Visual Studio 在设置项目时创建了这个文件夹。

提示:您无需遵循它或大多数其它的 MVC 约定,但我还是建议你这么做——尤其是因为它将帮助你理解这本书中的例子。

Visual Studio 向项目添加了一个默认控制器类,如果您在【Solution Explorer】中展开【Controllers】文件夹,就可以看到该类。该文件名为 HomeController.cs。控制器类包含一个名称,后面跟着单词:Controller,这意味着当看到一个名为 HomeController.cs 的文件时,就知道它是包含一个名为 HOME 的控制器,它是 MVC 应用程序中使用的默认控制器。单击【Solution Explorer】中的 HomeController.cs 文件,以便 Visual Studio 打开它进行编辑。

清单 2-1:【Controllers】文件夹下的 HomeController.cs 文件的初始内容

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using demo.Models;

namespace PartyInvites.Controllers
{
    public class HomeController : Controller
    {
        public IActionResult Index() {
            return View();
        }

        public IActionResult About() {
            ViewData["Message"] = "Your application description page.";

            return View();
        }

        public IActionResult Contact() {
            ViewData["Message"] = "Your contact page.";

            return View();
        }

        public IActionResult Privacy()
        {
            return View();
        }

        public IActionResult Error() {
            return View(new ErrorViewModel { RequestId = Activity.Current?.Id 
            ?? HttpContext.TraceIdentifier });
        }
    }
}

HomeController.cs 文件中的代码替换为清单2-2所列代码。除一个方法之外我删除了所有方法,更改了结果类型及其实现,并删除了未使用的命名空间的Using语句。

using Microsoft.AspNetCore.Mvc;

namespace PartyInvites.Controllers {
    public class HomeController : Controller {

        public string Index() {
            return "Hello World";
        }
    }
}

这些更改不会产生明显的效果,但它们会产生很好的示范。我已经更改了一个名为Index的方法,以便它返回字符串Hello World。通过从Visual Studio的【Debug】菜单中择【Start Debugging 】再次运行该项目。

提示:如果从上一节开始运行应用程序,则从【Debugging】菜单中选择【Restart】,如果愿意,则选择【Stop Debugging】,然后再选择【Start Debugging】。

浏览器将向服务器发出 HTTP 请求。默认的 MVC 配置意味着请求将使用Index方法(称为 action方法或仅称为 action)进行处理,来自该方法的结果将被发送回浏览器,如图2-8所示。 图2-8 action 方法的输出

提示:请注意,Visual Studio 已经将浏览器引导到端口57628。您几乎肯定会在浏览器请求的 URL 中看到不同的端口号,因为 Visual Studio 在创建项目时会分配一个随机端口。如果您查看 Windows 任务栏通知区域,将找到 IIS Express 的图标。这是一个完整 IIS 应用服务器的精简版本,包含在 Visual Studio 中,用于在开发过程中交付 ASP.NET Core 内容和服务。我将在第12章中向您展示如何将 MVC 项目部署到生产环境中。

除了模型、视图和控制器之外,MVC应用程序还使用 ASP.NET 路由系统,该系统决定如何将 URL 映射到控制器和 action。路由是用来决定如何处理请求的规则。当 Visual Studio 创建 MVC 项目时,它添加了一些默认路由来启动。您可以请求以下任何一个URL,它们将被定向到 HomeController 上的Index action:

  • /
  • /Home
  • /Home/Index

因此,当浏览器请求 http://yoursite/ 或 http://yoursite/Home 时,它会从 HomeController 的 Index方法中获得输出。您可以通过在浏览器中更改 URL 以自行尝试。目前,它将是 http://localhost:57628/,但端口部分可能有所不同。如果将 /Home 或 /Home/Index 附加到url,将看到 MVC 应用程序返回相同的结果:“Hello World”。

这是从 ASP.NET Core MVC 实现的以下约定中获益的一个很好的例子。在本例中,约定是我将有一个名为 HomeController 的控制器,它将成为 MVC 应用程序的起点。Visual Studio为新项目创建的默认配置假设我将遵循这个约定。因为我遵循了惯例,所以我自动获得了前面列表中的 URL 的支持。在前面的列表中。如果没有遵循约定,则需要修改配置以指向我创建的控制器。对于这个简单的例子,默认配置是我所需要的。

渲染 Web 页面

上例的输出不是 HTML —— 只是字符串 Hello World。要生成对浏览器请求的html响应,需要一个视图,它告诉 MVC 对浏览器的请求如何生成响应。

创建和渲染视图

我需要做的第一件事是修改我的Index action 方法,如清单2-3所示。更改之处用粗体显示,这是我在本书中遵循的惯例,以使示例更易于修改。

清单 2-3:在 Controllers 文件夹的 HomeController.cs 文件中渲染视图

using Microsoft.AspNetCore.Mvc;
namespace PartyInvites.Controllers {

    public class HomeController : Controller {

        public ViewResult Index() {
            return View("MyView");
        }
    }
}

当从一个 action 方法返回一个ViewResult对象时,我指示 MVC 渲染一个视图。我通过调用View方法来创建ViewResult对象,指定我想要使用的视图的名称,即MyView。如果运行应用程序,您可以看到 MVC 试图查找视图,并显示如图2-9所示的错误消息。 图2-9 MVC 试图寻找一个视图

这是一个有用的错误消息。它解释了 MVC 找不到我为 action 方法指定的视图,也显示了它的查看位置。视图存储在 Views 文件夹中,组织成子文件夹。例如,与 Home 控制器相关联的视图存储在一个名为 Views/Home 的文件夹中。不特定于单个控制器的视图存储在 Views/Shared 的文件夹中。Visual Studio 在使用【Web Application (Model-View-Controller)】模板时自动创建 Home 文件夹和 Shared 文件夹,并在项目开始时放入一些占位符视图。

要创建此示例所需的视图,请在【解决方案资源管理器】中展开【Views】文件夹,右键单击【Home】文件夹,然后从弹出菜单中选择【Add】➤【New Item】。Visual Studio 将向您显示项目模板列表。请在左窗格向下查看【ASP.NET Core】➤【Web】➤【ASP.NET】类别,然后在中央窗格选择【MVC View Page】项。如图2-10所示(不要使用与 MVC 框架无关的【Razor Page】模板)。

提示:您将看到【Views】文件夹中的一些现有文件,这些文件是由 Visual Studio 添加到项目中的一些初始内容,如图2-6所示,您可以忽略这些文件。

图2-10 创建一个视图

将名称设置为 MyView.cshtml,点击【Add】按钮来创建视图。Visual Studio将创建 Views/Home/MyView.cshtml 文件,打开并编辑它。视图文件的初始内容只是一些注释和占位符。用清单2-4所示的内容替换它们。

提示:很容易在错误的文件夹中创建视图文件。如果在【Views/Home】文件夹中没有一个名为 MyView.cshtml 的文件,那么删除所创建的文件,然后再试一次。

清单2-4:替换【Views/Home】文件夹中 MyView.cshtml 文件的内容

@{
    Layout = null;
}
<!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>Index</title>
</head>
<body>
    <div>
        Hello World (from the view)
    </div>
</body>
</html>

视图文件的新内容主要是 HTML。例外的部分如下:

...
@{
    Layout = null;
}
...

这是一个将由 Razor 视图引擎解析的表达式,它处理视图的内容并生成发送给浏览器的 HTML。这是一个简单的 Razor 表达式,它告诉 Razor 我选择不使用布局,这就像将发送到浏览器的 HTML 模板(我将在第5章中描述)。先暂时忽略 Razor,稍后再讲述它。要查看创建视图的效果,请从【Debug】菜单中选择【Start Debugging】来运行应用程序。您应该看到图2-11所示的结果。 图2-11 测试视图

当我第一次编辑Index action方法时,它返回了一个字符串值。这意味着 MVC 除了将字符串值传递给浏览器之外什么也不做。现在,Index方法返回一个 ViewResult,MVC 渲染视图并返回它产生的 HTML。我告诉 MVC 应该使用哪个视图,所以它使用命名约定自动找到它。该约定是:视图具有 action 方法的名称,并且包含在以控制器命名的文件夹中:/Views/Home/MyView.cshtml

除了字符串和ViewResult对象之外,还可以从 action 方法返回其他结果。例如,如果返回一个RedirectResult,浏览器将被重定向到另一个 URL。如果返回HttpUnauthorizedResult,可以提示用户登录。这些对象统称为 action results。action results 系统允许您在 action 中封装和重用常见的响应。我将告诉您更多关于它们的信息。并在第17章中解释它们的不同用法。

添加动态输出

Web 应用平台的重点是构建和显示动态输出,在 MVC 中,构建一些数据并将其传递给视图是控制器的工作,视图负责将数据渲染成 HTML。

将数据从控制器传递到视图的一种方法是使用ViewBag对象,它是Controller基类的一个成员。ViewBag是一个动态对象,可以为其分配任意属性,使这些值在随后渲染的任何视图中可用。清单2-5演示了以这种方式在 HomeController.cs 文件中传递一些简单的动态数据。

清单 2-5:设置 Controllers 文件夹下的 HomeController.cs 文件的视图数据

using System;
using Microsoft.AspNetCore.Mvc;

namespace PartyInvites.Controllers
{
    public class HomeController : Controller
    {
        public ViewResult Index()
        {
            int hour = DateTime.Now.Hour;
            ViewBag.Greeting = hour < 12 ? "Good Morning" : "Good Afternoon";
            return View("MyView");
        }
    }
}

在给ViewBag.Greeting属性赋值时,就为视图提供了数据。Greeting属性直到我分配值的那一刻才存在 —— 这允许我以自由和流畅的方式将数据从控制器传递到视图,而不必提前定义类。我在视图中再次引用ViewBag.Greeting属性以获取数据值,清单2-6展示了 MyView.cshtml 文件所做的修改。

清单 2-6:Views/Home 文件夹下的 MyView.cshtml 文件,取回 ViewBag 数据值

@{
    Layout = null;
}
<!DOCTYPE html>
<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>Index</title>
</head>
<body>
    <div>
        @ViewBag.Greeting World (from the view)
    </div>
</body>
</html>

清单中增加的是一个 Razor 表达式,当 MVC 使用视图生成一个响应时,对该表达式进行评估。当我在控制器的Index方法中调用View方法时,MVC 定位 MyView.cshtml 视图文件,并要求 Razor 视图引擎解析文件的内容。Razor 查找类似我在清单中添加的表达式并处理它们。在本例中,处理表达式意味着将为 action 方法中的ViewBag.Greeting属性分配的值插入到视图中。

属性名称Greeting没有什么特别之处,您可以用任何属性名称替换它,也会起同样的作用,只要在控制器中使用的名称与在视图中使用的名称相匹配。可以通过为多个属性赋值将多个数据值从控制器传递到视图中。通过启动项目,可以看到这些更改的效果,如图2-12所示。

图2-12 MVC动态响应

创建一个简单的数据输入应用程序

在本章的剩余部分,我将通过构建一个简单的数据输入应用程序来探索更多的 MVC 基本特性。我将在这个部分加快步伐。我的目标是演示 MVC 的 action,所以我将跳过一些关于场景背后运作原理的解释。但是不要担心,我将在后面的章节中深入地讨论这些主题。

场景设定

想象一下,一个朋友决定举办一个新年晚会,她让我创建一个网络应用程序,让她的受邀者以电子方式进行 RSVP。她要求提供以下四个关键功能:

  • 一个显示派对信息的主页
  • 一个可用于 RSVP 的表单
  • RSVP 表单的验证,它将显示感谢页
  • 一个显示谁来参加聚会的摘要页

在下面的部分,我将构建我在本章开头创建的 MVC 项目,并添加这些特性。我可以通过应用前面提到的内容来检查列表中的第一项,并将一些 HTML 添加到我现有的视图中,给出派对的细节。首先,清单2-7显示了我对视图 Views/Home/MyView.cshtml 文件所做的添加。

清单 2-7:Views/Home 文件夹下的 MyView.cshtml 文件,显示派对细节

@{
    Layout = null;
}
<!DOCTYPE html>
<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>Index</title>
</head>
<body>
    <div>
        @ViewBag.Greeting World (from the view)
        <p>We're going to have an exciting party.<br />
        (To do: sell it better. Add pictures or something.)
        </p>
    </div>
</body>
</html>

我们在路上了。如果你运行这个应用程序,通过从【Debug】菜单中选择【Start Debugging】,你会看到派对的细节(嗯,现在只是细节的占位符,但你已经有了想法),如图2-13所示。 图2-13 添加到视图的 HTML

设计数据模型

在 MVC 中,M 代表模型,它是应用程序最重要的部分。模型是定义应用程序主题(称为域)的真实世界对象、进程和规则的表示。该模型,通常被称为域模型,包含构成应用程序世界的 C# 对象(称为域对象)以及操作它们的方法。视图和控制器以一致的方式向客户端公开域,设计良好的 MVC 应用程序始于设计良好的模型,然后作为控制器和视图添加的焦点。

PartyInvites 项目不需要一个很复杂的模型,因为它是如此简单,我只需要一个域类,我将调用GuestResponse。这个对象将负责存储、验证和确认一个 RSVP。

MVC 的约定是将组成模型的类放置在一个名为 Models 的文件夹中,当使用【Web Application (Model-View-Controller)】模板时,Visual Studio 会自动创建该文件夹。

要创建类文件,右键单击【Solution Explorer】中的【Models】文件夹,并从弹出菜单中选择【Add】➤【Class】。将新类的名称设置为 GuestResponse.cs,然后单击【Add】按钮。编辑新类文件的内容以与清单2-8相匹配。

清单 2-8:Models 文件夹下的 GuestResponse.cs 文件的内容

namespace PartyInvites.Models {
    public class GuestResponse {
        public string Name { get; set; }
        public string Email { get; set; }
        public string Phone { get; set; }
        public bool? WillAttend { get; set; }
    }
}

提示:您可能已经注意到,WillAttend属性是一个可空的 bool值,这意味着它可以是truefalsenull。我在本章后面的“添加验证”这一节中解释了其基本原理。

创建第二个 Action 和强类型视图

应用程序目标之一是包含一个 RSVP 表单,这意味着需要定义一个可以接收对该表单的请求的 action 方法。单个控制器类可以定义多个 action 方法,而约定是将相关的操作组合在同一个控制器中。清单2-9显示了在 Home 控制器中添加一个新的 action 方法。

清单 2-9:Controllers 文件夹下的 HomeController.cs 文件

using System;
using Microsoft.AspNetCore.Mvc;

namespace PartyInvites.Controllers {
    public class HomeController : Controller {
        public ViewResult Index() {
            int hour = DateTime.Now.Hour;
            ViewBag.Greeting = hour < 12 ? "Good Morning" : "Good Afternoon";
            return View("MyView");
        }
        public ViewResult RsvpForm() {
            return View();
        }
    }
}

RsvpForm action 方法在调用View方法时并没有使用参数,这表示通知 MVC 去渲染与 action 方法相关联的默认视图,此视图有着与 action 方法相同的名称,本例为 RsvpForm.cshtml。

右键单击 Views/Home 文件夹,在弹出菜单中选择【Add】➤【New Item】。选择【MVC View Page】模板,设置新文件名为 RsvpForm.cshtml,并单击【Add】按钮创建文件。更改文件内容以匹配清单2-10。

清单2-10:Views/Home 文件夹下的 RsvpForm.cshtml 文件,设置内容。

@model PartyInvites.Models.GuestResponse

@{
    Layout = null;
}
<!DOCTYPE html>
<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>RsvpForm</title>
</head>
<body>
    <div>
        This is the RsvpForm.cshtml View
    </div>
</body>
</html>

内容大部份为 HTML,加入了适量的 Razor 表达式@model,它用于创建一个强类型视图。强类型视图用于渲染特定的模型类型,如果我指定要使用的类型(此例是 PartyInvites.Models 命名空间中的 GuestResponse 类),MVC 可以创建一些有用的快捷方式来使它更易于使用。我将很快利用到强类型特性。

要测试新的 action 方法和它的视图,请在【Debug】菜单中选择【Start Debugging】并将浏览器导航至 URL: /Home/RsvpForm

MVC 将按照我之前描述的命名约定,直接请求 Home 控制器定义的RsvpForm action 方法。此 action 方法通知 MVC 渲染默认视图,通过命名约定的另一个应用,渲染 Views/Home 文件夹下的 RsvpForm.cshtml。 图2-14 渲染第二个视图

链接 Action 方法

我希望能够从 MyView 视图创建一个链接,这样客人就可以看到 RsvpForm 视图,而不必知道针对特定 action 方法的 URL,如清单2-11所示。

清单 2-11:Views/Home 文件夹下的 MyView.cshtml 文件,添加一个到 RSVP 表单的链接。

@{
    Layout = null;
}
<!DOCTYPE html>
<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>Index</title>
</head>
<body>
    <div>
        @ViewBag.Greeting World (from the view)
        <p>We're going to have an exciting party.<br />
            (To do: sell it better. Add pictures or something.)
        </p>
        <a asp-action="RsvpForm">RSVP Now</a>
    </div>
</body>
</html>

清单中添加的是一个具有asp-action属性的a元素。它是标签助手属性的一个示例,它是在视图渲染时被执行的 Razor 指令。asp-action属性是将href属性添加到一个元素的指令,使得该元素可以包含 action 方法所对应的 URL。我在24、25和26章解释标签助手是如何工作的,这个指令对元素来说是最简单的标签助手属性类型,它告诉 Razor 为当前视图所渲染的同一个控制器定义的 action 方法插入一个 URL。您可以看到项目启动后助手所创建的链接,如图2-15所示。 图2-15 action 方法的链接

启动应用程序并将鼠标移动到浏览器的【RSVP Now】链接上方,您将看到链接点所对应的 URL(Visual Studio 给项目分配的端口号可能会有所不同):

http://localhost:57628/Home/RsvpForm

这里有一个重要的原则,就是应该使用 MVC 提供的功能来生成URL,而不是将它们硬编码到视图中。当标记助手为一个元素创建href属性时,它检查应用程序的配置以确定 URL 的内容。这允许更改应用程序的配置以支持不同的 URL 格式而无需更新任何视图。我在第15章中解释了它是如何工作的。

构建表单

现在我已经创建了强类型视图,并且可以从 Index 视图到达它。接下来构建 RsvpForm.cshtml 文件的内容,使其成为用于编辑GuestResponse对象的 HTML 表单,如清单2-12所示。

清单 2-12:Views/Home 文件夹下的 RsvpForm.cshtml 文件,创建表单视图

@model PartyInvites.Models.GuestResponse

@{
    Layout = null;
}
<!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>RsvpForm</title>
</head>
<body>
    <form asp-action="RsvpForm" method="post">
        <p>
            <label asp-for="Name">Your name:</label>
            <input asp-for="Name" />
        </p>
        <p>
            <label asp-for="Email">Your email:</label>
            <input asp-for="Email" />
        </p>
        <p>
            <label asp-for="Phone">Your phone:</label>
            <input asp-for="Phone" /></p>
        <p>
            <label>Will you attend?</label>
            <select asp-for="WillAttend">
                <option value="">Choose an option</option>
                <option value="true">Yes, I'll be there</option>
                <option value="false">No, I can't come</option>
            </select>
        </p>
        <button type="submit">Submit RSVP</button>
    </form>
</body>
</html>

我已经为GuestResponse模型类的每个属性定义了一个labelinput元素(对于WillAttend属性来说是select元素)。每个元素都通过asp-for属性与模型的属性进行关联,asp-for是另一个标签助手属性。标签助手属性配置元素以将它们绑定到模型对象。下面是标签助手生成并发送给浏览器的 HTML 示例:

<p>
    <label for="Name">Your name:</label>
    <input type="text" id="Name" name="Name" value="">
</p>

label元素上的asp-for属性设置了for属性。input元素上的asp-for属性则设置了idname属性。这在目前看来并不是特别有用,但是您将看到,在定义应用程序功能时,将元素与模型属性相关联提供的额外的优势。

更直观的是应用于form元素的asp-action属性,它通过应用程序的 URL 路由配置将 action 属性设置为针对特定 action 方法的 URL,如下所示:

<form method="post" action="/Home/RsvpForm">

与我应用于a元素的助手属性一样,这种方法的好处是在更改应用程序使用的 URL 系统时,标签助手生成的内容将自动反映更改。

可以通过运行程序并单击 RSVP Now 链接打开表单,如图2-16所示。 图2-16 为应用程序添加一个 HTML 表单

接收表单数据

当表单提交到服务器时,我还没有告诉 MVC 我想做什么。按目前的情况,单击 Submit RSVP 按钮只会清除您输入到表单中的任何值。这是因为表单回发到 Home 控制器中的 RsvpForm 操作方法只是告诉 MVC 再次渲染视图。为了接收和处理提交的表单数据,我将使用核心控制器功能,添加第二个 RsvpForm action 方法来创建以下内容:

  • 一个响应 HTTP GET 请求的方法:GET 请求是浏览器在每次单击链接时通常会发出的问题。此版本的 action 将负责在第一次访问 /home/RsvpForm 时显示初始空白表单。
  • 一个响应 HTTP POST 请求的方法:默认情况下,使用Html.BeginForm()渲染的表单由浏览器作为 POST 请求提交。此版本的 action 将负责接收已提交的数据并决定如何处理它。

在不同的 C# 方法中分别处理 GET 和 POST 请求有助于保持控制器代码的整洁,因为两个方法的职责不同。两个 action 方法都由同一个 URL 调用,但是 MVC 会根据处理的请求是 GET 还是 POST 来确保调用适当的方法。清单 2-13 显示了 HomeController 类的更改。

清单 2-13:Controller 文件夹下的 HomeController.cs 文件,添加一个方法

using System;
using Microsoft.AspNetCore.Mvc;
using PartyInvites.Models;

namespace PartyInvites.Controllers {
    public class HomeController : Controller {
        public ViewResult Index() {
            int hour = DateTime.Now.Hour;
            ViewBag.Greeting = hour < 12 ? "Good Morning" : "Good Afternoon";
            return View("MyView");
        }
        [HttpGet]
            public ViewResult RsvpForm() {
            return View();
        }
        [HttpPost]
        public ViewResult RsvpForm(GuestResponse guestResponse) {
            // TODO: store response from guest
            return View();
        }
    }
}

我已经将HttpGet特性添加到现有的RsvpForm action 方法中,以告诉 MVC 这个方法应该只用于 GET 请求。然后我添加了RsvpForm方法的重载版本,它接受一个GuestResponse对象。我将HttpPost特性应用于这个方法,这告诉 MVC 新方法将处理 POST 请求。我将在之后解释这些清单中新添加的代码是如何工作的。我还引入了PartyInvites.Models命名空间,这样就可以在不需要限定类名的情况下引用GuestResponse模型类型。

使用模型绑定

RsvpForm action 方法的第一个重载渲染与前面相同的视图 —— RsvpForm.cshtml 文件,以生成如图2-16所示的表单。由于参数的存在,第二个重载更有趣,但考虑到 action 方法将在响应 HTTP POST 请求时被调用,并且 GuestResponse 类型是一个 C# 类,两者是如何联系起来的?

答案是模型绑定,这是一种有用的 MVC 功能,通过它解析传入的数据,并将 HTTP 请求中的键/值对填充远程模型类型的属性。

模型绑定是一个强大的、可定制的功能,它消除了直接处理 HTTP 请求的繁琐工作,让您直接处理 C# 对象而不是由浏览器发送的单个数据值。作为参数传递给 action 方法的GuestResponse对象将自动填充表单域中的数据。我在第26章中深入研究了模型绑定的细节,包括如何定制它。

应用程序的目标之一是提供一个包含参与者详细信息的摘要页,这意味着我需要跟踪收到的响应。我将通过创建一个内存中的对象集合来实现这一点。在实际的开发中不会这样做,因为当应用程序停止或重新启动时,响应数据将丢失,但这样做使得我可以继续专注于 MVC,并创建一个可以轻松重置到其初始状态的应用程序。

提示:我在第8章中演示了如何使用 MVC 持久地存储和访问数据,作为一个更实际的示例应用程序的一部分,该应用程序名为 SportsStore。

右键单击 Models 文件夹并从弹出菜单中选择【Add】➤【Class】向项目添加了一个文件。将文件的名设置为 Repository.cs,并使用它来定义清单2-14所示的类。

清单2-14:Models 文件夹下的 Repository.cs 文件的内容

using System.Collections.Generic;

namespace PartyInvites.Models {
    public static class Repository {
        private static List<GuestResponse> responses = new List<GuestResponse>();
        public static IEnumerable<GuestResponse> Responses {
            get {
                return responses;
            }
        }
        public static void AddResponse(GuestResponse response) {
            responses.Add(response);
        }
    }
}

Repository类及其成员设置为静态可以方便应用程序从不同位置存储和检索数据。MVC 提供了一种更复杂的方法来定义公共功能,称为依赖注入,我在第18章中对此进行了描述,但静态类是启动此类简单应用程序的好方法。

存储响应

现在有地方可以存储数据了,我可以更新 action 方法以接收 HTTP POST 请求,如清单 2-15 所示。

清单2-15:更新 HomeController.cs 文件的 Action 方法

using System;
using Microsoft.AspNetCore.Mvc;
using PartyInvites.Models;

namespace PartyInvites.Controllers {
    public class HomeController : Controller {
        public ViewResult Index() {
            int hour = DateTime.Now.Hour;
            ViewBag.Greeting = hour < 12 ? "Good Morning" : "Good Afternoon";
            return View("MyView");
        }
        [HttpGet]
        public ViewResult RsvpForm() {
            return View();
        }
        [HttpPost]
        public ViewResult RsvpForm(GuestResponse guestResponse) {
            Repository.AddResponse(guestResponse);
            return View("Thanks", guestResponse);
        }
    }
}

在本例中,要处理请求中发送的表单数据,我所要做的就是处理传递给 action 方法的GuestResponse对象,将其作为参数传递给Repository.AddResponse方法,以便能够存储响应。

为何模型绑定与 Web Forms 不一样?

在第1章中,我解释了传统 ASP.NET Web Forms 的缺点之一,即它向开发者隐藏了 HTTP 和 HTML 的细节。在清单2-15中我通过 HTTP POST 请求创建了一个GuestResponse对象,您可能想知道,此 MVC 模型绑定是否也在做同样的事情。

当然不是,模型绑定让我从繁琐、易错的任务中摆脱出来,即必须检查 HTTP 请求并提取需要的所有数据值,但是(这是重要的部分),如果我想手动处理请求,可以这样做,因为 MVC 提供了对所有请求数据的轻松访问。它未对开发人员有任何隐藏,有许多有用的功能让 HTTP 和 HTML 的使用变得更为简单和容易;当然,使用这些功能是可选的。

这似乎是一个微妙的差别,但随着您了解到更多关于 MVC 的知识,将会看到它与传统的 Web Forms 的开发经验完全不同,并且您始终会知道应用程序接收的请求是如何处理的。

对 RsvpForm action 方法中的View方法的调用告诉 MVC 渲染一个名为 Thanks 的视图,并将 GuestResponse 对象传递给视图。要创建视图,右键单击【Solution Explorer】中的 Views/Home 文件夹,然后从弹出菜单中选择【Add】➤【New Item】。选择 ASP.NET 类别中的【MVC View Page】模板,将名称设置为 Thanks.cshtml,然后单击【Add】按钮。Visual Studio 将创建视图文件 Views/Home/Thanks.cshtml,并打开它进行编辑。更改文件的内容,使其与清单2-16相匹配。

清单 2-16:Views/Home 文件夹下的 Thanks.cshtml 文件的内容

@model PartyInvites.Models.GuestResponse
@{
    Layout = null;
}
<!DOCTYPE html>
<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>Thanks</title>
</head>
<body>
    <p>
        <h1>Thank you, @Model.Name!</h1>
        @if (Model.WillAttend == true) {
            @:It's great that you're coming. The drinks are already in the fridge!
        } else {
            @:Sorry to hear that you can't make it, but thanks for letting us know.
        }
    </p>
    <p>Click <a asp-action="ListResponses">here</a> to see who is coming.</p>
</body>
</html>

Thanks.cshtml 视图使用 Razor 并基于 RsvpForm action 方法中传递给View方法的GuestResponse属性的值显示相应内容。

要访问远程对象中的属性的值,请使用Model.PropertyName.。例如,为了获得Name属性的值,我调用了Model.Name.。不要担心无法理解 Razor 语法 —— 我在第5章中对它做了更详细的解释。

现在我已经创建了 Thanks 视图,已经有了一个使用 MVC 处理表单的基本工作示例。在 Visual Studio 中通过从【Debug】菜单中选择【Start Debugging】启动应用程序,单击 RSVP Now 链接,向表单添加一些数据,然后单击 Submit RSVP 按钮。您将看到图2-17所示的结果(如果您的名字不是 Joe 或者您选择不能参加,结果会有所不同)。

图2-17 Thanks 视图

显示响应

在 Thanks.cshtml 视图的末尾,我添加了一个元素来创建一个链接,以显示要来聚会的人的列表。我使用asp-action标签助手属性创建了一个 URL,它的目标是一个名为ListResponses的 action 方法,如下所示:

...
<p>Click <a asp-action="ListResponses">here</a> to see who is coming.</p>
...

如果将鼠标悬停在浏览器显示的链接上,您将看到它以 /Home/ListResponses URL 为目标。这与 Home 控制器中的任何 action 方法都不对应,如果单击该链接,您将看到 404 NotFound 错误页面。

我将在 Home 控制器中通过创建 URL 所针对的 action 方法来解决这个问题,如清单2-17所示。

清单 2-17:Controllers 文件夹下的 HomeController.cs 文件,添加一个 Action 方法

using System;
using Microsoft.AspNetCore.Mvc;
using PartyInvites.Models;
using System.Linq;

namespace PartyInvites.Controllers {
    public class HomeController : Controller {
        public ViewResult Index() {
            int hour = DateTime.Now.Hour;
            ViewBag.Greeting = hour < 12 ? "Good Morning" : "Good Afternoon";
            return View("MyView");
        }
        [HttpGet]
        public ViewResult RsvpForm() {
            return View();
        }
        [HttpPost]
        public ViewResult RsvpForm(GuestResponse guestResponse) {
            Repository.AddResponse(guestResponse);
            return View("Thanks", guestResponse);
        }
        public ViewResult ListResponses() {
            return View(Repository.Responses.Where(r => r.WillAttend == true));
        }
    }
}

新的 action 方法称为ListResponses,它调用View方法,使用Repository.Responses属性作为参数。这演示了一个 action 方法如何向强类型视图提供数据。GuestResponse对象集合使用 LINQ 进行过滤,因此只使用正面响应。

ListResponses action 方法没有指定用于显示GuestResponse对象集合的视图的名称,这意味着将使用默认的命名约定,MVC 将在视图 Views/Home 和 Views/Shared 中查找一个名为 ListResponses.cshtml 的视图。要创建该视图,右键单击【Solution Explorer】中的 Views/Home 文件夹,然后在弹出菜单中选择【Add】➤【New Item】。选择【MVC View Page】模板,将名称设置为 ListResponses.cshtml,然后单击【Add】按钮。编辑新视图的内容,以匹配清单2-18。

清单 2-18:Views/Home 文件夹下的 ListResponses.cshtml 文件,显示接受邀请者信息

@model IEnumerable<PartyInvites.Models.GuestResponse>

@{
    Layout = null;
}
<!DOCTYPE html>
<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>Responses</title>
</head>
<body>
    <h2>Here is the list of people attending the party</h2>
    <table>
        <thead>
            <tr>
                <th>Name</th>
                <th>Email</th>
                <th>Phone</th>
            </tr>
        </thead>
        <tbody>
            @foreach (PartyInvites.Models.GuestResponse r in Model) {
                <tr>
                    <td>@r.Name</td>
                    <td>@r.Email</td>
                    <td>@r.Phone</td>
                </tr>
            }
        </tbody>
    </table>
</body>
</html>

Razor 视图文件的扩展名为 cshtml,因为它混合了 C# 代码和 HTML 元素。您可以查看清单2-18,我使用了一个foreach循环来处理每个由 action 方法使用View方法传递到视图的GuestResponse对象。与普通的 C# foreach循环不同,Razor foreach循环包含的 HTML 元素主体将被添加到响应中,并返回给浏览器。在此视图中,每个GuestResponse对象生成一个包含td元素的tr元素,每个td元素由对象属性值填充。

若要查看工作列表,请从【Start】菜单中选择【Start Debugging】运行应用程序,提交一些表单数据,然后单击链接查看响应列表。您将看到自应用程序启动以来输入的数据的摘要,如图2-18所示。视图没有以吸引人的方式显示数据,但现阶段已足够,我将在本章后面讨论应用程序的样式。

图2-18 显示参加聚会的名单

添加验证

现在我可以将数据验证添加到应用程序中。如果没有验证,用户可以输入毫无意义的数据,甚至提交一个空的表单。在 MVC 应用程序中,您通常在远程模型而不是在用户界面中使用验证。这意味着您在一个地方定义了验证,但在应用程序中模型类使用的任何地方都会生效。MVC 支持声明式验证规则,它在System.ComponentModel.DataAnnotations命名空间中以特性的方式定义,这意味着验证约束是使用标准的 C# 特性来表示的。

清单 2-19:Models 文件夹下的 GuestResponse.cs文件,使用验证

using System.ComponentModel.DataAnnotations;

namespace PartyInvites.Models {
    public class GuestResponse {
        [Required(ErrorMessage = "Please enter your name")]
        public string Name { get; set; }
        [Required(ErrorMessage = "Please enter your email address")]
        [RegularExpression(".+\\@.+\\..+",
        ErrorMessage = "Please enter a valid email address")]
        public string Email { get; set; }
        [Required(ErrorMessage = "Please enter your phone number")]
        public string Phone { get; set; }
        [Required(ErrorMessage = "Please specify whether you'll attend")]
        public bool? WillAttend { get; set; }
    }
}

MVC 自动检测特性并在模型绑定的过程中使用它们验证数据。我引入了包含验证特性的命名空间,这样在使用它们的时候就不需要输入限定名称了。

提示:如前所述,我使用了一个可空布尔类型的WillAttend属性。这样做是为了能够应用所需的验证特性。如果使用了一个普通的 bool 类型,通过模型绑定接收到的值只能是 truefalse,并且我无法判断用户是否已经选择了一个值。可空布尔值有三个可能的值:true、false 和 null。如果用户没有选择值,浏览器将发送一个 null,这会导致 Required 特性报告验证错误。这是 MVC 如何优雅地将 C# 特性与 HTML 以及 HTTP 完美地结合在一起的一个很好的例子。

我在控制器类中使用ModelState.IsValid属性检查是否存在验证问题。清单2-20显示了如何在 Home 控制器类中的接收 POST 的RsvpForm action 方法中完成此操作。

清单 2-20:Controllers 文件夹下的 HomeController.cs 文件,检查验证错误

using System;
using Microsoft.AspNetCore.Mvc;
using PartyInvites.Models;
using System.Linq;

namespace PartyInvites.Controllers {
    public class HomeController : Controller {
        public ViewResult Index() {
            int hour = DateTime.Now.Hour;
            ViewBag.Greeting = hour < 12 ? "Good Morning" : "Good Afternoon";
            return View("MyView");
        }
        [HttpGet]
        public ViewResult RsvpForm() {
            return View();
        }
        [HttpPost]
        public ViewResult RsvpForm(GuestResponse guestResponse) {
            if (ModelState.IsValid) {
                Repository.AddResponse(guestResponse);
                return View("Thanks", guestResponse);
            } else {
                // there is a validation error
                return View();
            }
        }
        public ViewResult ListResponses() {
            return View(Repository.Responses.Where(r => r.WillAttend == true));
        }
    }
}

Controller基类提供了一个叫ModelState的属性,它提供了有关将 HTTP 请求数据转换为 C# 对象的信息。如果ModelState.IsValid属性返回true,就说明 MVC 已经满足了我在GuestResponse类上通过特性指定的验证约束。当发生这些情况时,我象之前一样,渲染了 Thanks 视图。

如果ModelState.IsValid属性返回false,我就知道存在验证错误。由ModelState属性返回的对象会提供已经遇到的每个问题的细节,但是我不需要进入这个层次的细节,因为可以依赖于一个有用的特性,它通过调用没有任何参数的View方法来使要求用户解决任何问题的过程自动化。

当 MVC 渲染视图时,Razor 可以访问与请求相关的任何验证错误的细节,而标签助手可以访问这些细节以向用户显示验证错误。清单2-21演示了向 RsvpForm 视图添加验证标签助手属性。

清单2-21:Views/Home 文件夹下的 RsvpForm.cshtml 文件,添加验证摘要

@model PartyInvites.Models.GuestResponse

@{
    Layout = null;
}
<!DOCTYPE html>
<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>RsvpForm</title>
</head>
<body>
    <form asp-action="RsvpForm" method="post">
        <div asp-validation-summary="All"></div>
        <p>
            <label asp-for="Name">Your name:</label>
            <input asp-for="Name" />
        </p>
        <p>
            <label asp-for="Email">Your email:</label>
            <input asp-for="Email" />
        </p>
        <p>
            <label asp-for="Phone">Your phone:</label>
            <input asp-for="Phone" /></p>
        <p>
        <label>Will you attend?</label>
        <select asp-for="WillAttend">
            <option value="">Choose an option</option>
            <option value="true">Yes, I'll be there</option>
            <option value="false">No, I can't come</option>
        </select>
        </p>
        <button type="submit">Submit RSVP</button>
    </form>
</body>
</html>

asp-validation-summary属性应用于div元素,当视图渲染时,它显示一个验证错误列表。asp-validation-summary属性值来自一个名为ValidationSummary的枚举值,它指定了摘要将包含哪些类型的验证错误。我将这个值指定为All,对于大多数应用程序来说,这是一个很好的起点,我在第27章中描述了其他值并解释了它们是如何工作的。

要查看验证摘要是如何工作的,请运行应用程序,填写 Name 字段,并在不输入任何其他数据的情况下提交表单。您将看到验证错误的摘要,如图2-19所示。

图2-19 显示验证错误

RsvpForm action 方法将不会渲染 Thanks 视图,直到应用于GuestResponse类的所有验证约束都得到满足。请注意,当 Razore 使用验证摘要渲染视图时,输入到Name字段的数据被保留并再次显示。这是模型绑定的另一个优点,它简化了表单数据的处理。

注意:如果您曾经使用过 ASP.NET Web Forms,会知道 Web Forms 有一个服务器控件的概念,即通过将值序列化到一个名为__VIEWSTATE的隐藏表单字段来保留状态。MVC 模型绑定与服务器控件、回发或视图状态等 Web Forms 概念无关。MVC 不向渲染的 HTML 页面中注入隐藏__VIEWSTATE字段。相反,它通过设置 input 元素的value属性来包含数据。

高亮无效字段

将模型属性与元素关联起来的标签助手属性具有与模型绑定结合使用的便利特性。当模型类属性验证失败时,助手属性将生成略微不同的 HTML。这里是没有验证错误时为 Phone 字段生成的输入元素:

<input type="text" data-val="true" data-val-required="Please enter your phone number"
id="Phone" name="Phone" value="">

作为比较,在用户提交表单而没有将任何数据输入text字段(这是验证错误,因为我将Required验证属性应用于GuestResponse类的Phone属性)之后,以下是同样的 HTML 元素:

<input type="text" class="input-validation-error" data-val="true"
    data-val-required="Please enter your phone number" id="Phone"
    name="Phone" value="">

我已经高亮了不同之处:asp-for标签助手属性为input元素添加了一个叫input-validation-errorclass。我可以利用这个特性,创建一个样式表,其中包含该class的 CSS 样式,以及不同 HTML 助手属性使用的其他样式。

MVC 项目的约定是将传递给客户端的静态内容在按内容类型组织的 wwwroot 文件夹内,所以 CSS 样式表就应该放在 wwwwroot/css 文件夹,JavaScript 文件则应当放在 wwwroot/js 文件夹,诸如此类。

要创建样式表,在 Visual Studio 【Solution Explorer】中右击 wwwroot/css 文件夹,选择【Add】➤ 【New Item】,导航到【ASP.NET Core】 ➤ 【Web】 ➤ 【Content】部分,并在模板列表中选择【Sytle Sheet】,如图2-20所示。

提示:当使用【Web Application】模板创建项目时,Visual Studio 在 wwwroot/css 文件夹下创建了一个 site.css 文件。您可以忽略此文件,本章不会使用它。

图2-20 创建 CSS 样式表

将文件名设置为 styles.css,点击【Add】按钮以创建样式,编辑新文件,使其包含清单2-22所示的样式。

清单 2-22:wwwroot/css 文件夹下的 styles.css 文件的内容

.field-validation-error {color: #f00;}
.field-validation-valid { display: none;}
.input-validation-error { border: 1px solid #f00; background-color: #fee; }
.validation-summary-errors { font-weight: bold; color: #f00;}
.validation-summary-valid { display: none;}

应用此样式,请在 RsvpForm 视图的head部分添加一个link元素。

清单 2-23:Views/Home 文件夹下的 RsvpForm.cshtml 文件,应用样式

<head>
    <meta name="viewport" content="width=device-width" />
    <title>RsvpForm</title>
    <link rel="stylesheet" href="/css/styles.css" />
</head>

link元素使用href属性指定样式表的位置。注意,URL 中省略了 wwwroot 文件夹。ASP.NET 的默认配置包括支持提供静态内容,如图像、CSS 样式表和 JavaScript文件,并自动将请求映射到 wwwroot 文件夹。我在第14章中描述了 ASP.NET 和 MVC 的配置过程。

提示:有一个特殊的标签助手来处理样式表,如果您有很多文件要管理的话,它非常有用。详情请参阅第25章。

应用程序使用了样式之后,在数据提交导致验证错误时,将显示得更为直观,如图2-21所示。 图2-21 自动高亮验证错误

内容样式化

应用程序的所有功能目标都已完成,但是应用程序的总体外观差强人意。当您使用【Web Application】模板创建一个项目时,正如我在本章中所做的那样,Visual Studio 安装了一些常见的客户端开发包。虽然我不喜欢使用模板项目,但喜欢微软所选择的客户端库。其中之一就是 Bootstrap,这是一个非常好的 CSS 框架,最初是由 Twitter 开发的,它本身已经成为一个主流的开源项目,并且已经成为 Web 应用程序开发的中流砥柱。

注意:Bootstrap 3 是我在写这些内容时的当前版本,但版本 4 正在开发中。微软可能会选择在 Visual Studio 后期版本中的【Web Application】模板中使用升级的 Bootstrap 版本,这可能会导致内容以不同的方式显示。这对于本书的其他章节来说不是问题,因为我将向您展示如何显式指定包版本,以获得预期的结果。

为 Welcome 视图添加样式

基本的 Bootstrap 功能通过将类应用于元素来工作,这些元素与被添加到 wwwroot/lib/bootstrap 文件夹下的 CSS 选择器相匹配。你可以从 http://getbootstrap.com获取 Bootstrap 定义的类的全部细节,但您可以看到我是如何对清单2-24中的 MyView.cshtml 视图文件应用一些基本样式的。

清单 2-24:Views/Home 文件夹下的 MyView.cshtml 文件,添加 Bootstrap

@{
    Layout = null;
}
<!DOCTYPE html>
<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>Index</title>
    <link rel="stylesheet" href="/lib/bootstrap/dist/css/bootstrap.css" />
</head>
<body>
    <div class="text-center">
        <h3>We're going to have an exciting party!</h3>
        <h4>And you are invited</h4>
        <a class="btn btn-primary" asp-action="RsvpForm">RSVP Now</a>
    </div>
</body>
</html>

我添加了一个link元素,通过href属性从 wwwroot/lib/bootstrap/dist/css 文件夹下载入 bootstrap.css 文件。按照惯例,第三方 CSS 和 JavaScript 包应当安装在 wwwroot/lib 文件夹,我在第6章中描述了用于管理这些包的工具。

导入了 Bootstrap 样式表之后,需要给我的元素添加样式。这是一个简单的例子,所以我只需使用少量 Bootstrap CSS 类:text-centerbtnbtn-primary

text-center类将一个元素和它的孩子的内容居中。btn类将buttoninput或一个元素的样式设置为漂亮的按钮。btn-primary类为按钮指定一系列颜色中的一种。通过运行应用程序来查看效果,如图2-22所示。 图2-22 给视图添加样式

很明显,我不是一个网页设计师。事实上,当我还是孩子时,被免除了艺术课程,因为我完全没有天赋。这有一个愉快的结果,让我有更多的时间上数学课,但这意味着我的艺术技能还没有发展到超过一般10岁孩子的水平。对于一个真正的项目,我会寻求一个专业人士帮助设计样式,但对于这个例子,我只能单独完成,这意味着我将尽可能的克制和一致地应用 Bootstrap。

为 RsvpForm 视图添加样式

Bootstrap 定义了可用于表单样式的类。我不打算详细介绍,但是您可以看到我是如何在清单2-25中使用这些类的。

清单 2-25:Views/Home 文件夹下的 RsvpForm.cshtml 文件,添加 Bootstrap

@model PartyInvites.Models.GuestResponse
@{
    Layout = null;
}
<!DOCTYPE html>
<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>RsvpForm</title>
    <link rel="stylesheet" href="/css/styles.css" />
    <link rel="stylesheet" href="/lib/bootstrap/dist/css/bootstrap.css" />
</head>
<body>
    <div class="panel panel-success">
        <div class="panel-heading text-center"><h4>RSVP</h4></div>
        <div class="panel-body">
            <form class="p-a-1" asp-action="RsvpForm" method="post">
                <div asp-validation-summary="All"></div>
                <div class="form-group">
                    <label asp-for="Name">Your name:</label>
                    <input class="form-control" asp-for="Name" />
                </div>
                <div class="form-group">
                    <label asp-for="Email">Your email:</label>
                    <input class="form-control" asp-for="Email" />
                </div>
                <div class="form-group">
                    <label asp-for="Phone">Your phone:</label>
                    <input class="form-control" asp-for="Phone" />
                </div>
                <div class="form-group">
                    <label>Will you attend?</label>
                    <select class="form-control" asp-for="WillAttend">
                        <option value="">Choose an option</option>
                        <option value="true">Yes, I'll be there</option>
                        <option value="false">No, I can't come</option>
                    </select>
                </div>
                <div class="text-center">
                    <button class="btn btn-primary" type="submit">
                        Submit RSVP
                    </button>
                </div>
            </form>
        </div>
    </div>
</body>
</html>

本例中的 Bootstrap 类创建了一个头部,只是为了结构化布局。为了对表单进行样式化,我使用了 form-group类,它用于对包含label和相关的inputselect的元素进行样式化。您可以在图2-23中看到样式的效果。 图2-23 给 RsvpForm 视图添加样式

给 Thanks 视图添加样式

下一个视图文件样式是 Thanks.cshtml,您可以看到我在清单2-26中是如何使用CSS类的,这些 CSS 类与我用于其他视图的类相似。为了使应用程序更易于管理,尽可能避免相同的代码和标记是一个很好的原则。MVC 提供了一些帮助减少重复的特性,会在后面的章节中描述。这些特性包括 Razor 布局(第5章)、分部视图(第21章)和视图组件(第22章)。

清单 2-26:Views/Home 文件夹下的 Thanks.cshtml 文件,应用 Bootstrap

@model PartyInvites.Models.GuestResponse

@{
    Layout = null;
}
<!DOCTYPE html>
<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>Thanks</title>
    <link rel="stylesheet" href="/lib/bootstrap/dist/css/bootstrap.css" />
</head>
<body class="text-center">
    <p>
        <h1>Thank you, @Model.Name!</h1>
        @if (Model.WillAttend == true) {
            @:It's great that you're coming. The drinks are already in the fridge!
        } else {
            @:Sorry to hear that you can't make it, but thanks for letting us know.
        }
    </p>
    Click <a class="nav-link" asp-action="ListResponses">here</a>
        to see who is coming.
</body>
</html>

图2-24显示了样式效果。 图2-24 Thanks 视图样式

给列表视图添加样式

最后需要添加样式的是 ListResponses,它显示了与会者列表。对内容进行样式设置遵循与所有 Bootstrap 样式相同的基本方法,如清单2-27所示。

清单 2-27:Views/Home 文件夹下的 ListResponses.cshtml 文件,添加 Bootstrap

@model IEnumerable<PartyInvites.Models.GuestResponse>

@{
    Layout = null;
}
<!DOCTYPE html>
<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <link rel="stylesheet" href="/lib/bootstrap/dist/css/bootstrap.css" />
    <title>Responses</title>
</head>
<body>
    <div class="panel-body">
        <h2>Here is the list of people attending the party</h2>
        <table class="table table-sm table-striped table-bordered">
            <thead>
                <tr>
                    <th>Name</th>
                    <th>Email</th>
                    <th>Phone</th>
                </tr>
            </thead>
            <tbody>
                @foreach (PartyInvites.Models.GuestResponse r in Model) {
                    <tr>
                        <td>@r.Name</td>
                        <td>@r.Email</td>
                        <td>@r.Phone</td>
                    </tr>
                }
            </tbody>
        </table>
    </div>
</body>
</html>

图2-25展示了与会者的显示方式。将这些样式添加到视图后,示例应用程序全部完成,现在已经满足了所有的开发目标,并且外观得到了改善。 图2-25 ListResponses 视图样式

总结

在本章中,我创建了一个新的 MVC 项目,并使用它构建一个简单的数据输入应用程序,让您第一次了解ASP.NET Core MVC体系结构和方法。我跳过了一些关键特性(包括 Razor 语法、路由和测试),但会在后面的章节中深入讨论这些主题。在下一章中,我将描述 MVC 设计模式,它构成了使用 ASP.NET Core MVC进行有效开发的基础。

;

© 2018 - IOT小分队文章发布系统 v0.3